#include "OgreGLESFBORenderTexture.h"
#include "OgreGLESPixelFormat.h"
#include "OgreLogManager.h"
#include "OgreRoot.h"
#include "OgreGLESHardwarePixelBuffer.h"
#include "OgreGLESFBOMultiRenderTarget.h"

namespace Ogre
{
	GLESFBORenderTexture::GLESFBORenderTexture(GLESFBOManager* manager, const String& name,
		const GLESSurfaceDesc& target, bool writeGamma, uint fsaa):
		GLESRenderTexture(name, target, writeGamma, fsaa),
		mFB(manager, fsaa)
	{
		mFB.bindSurface(0, target);
		GL_CHECK_ERROR;
		mWidth = mFB.getWidth();
		mHeight = mFB.getHeight();
	}
	
	void GLESFBORenderTexture::getCustomAttribute(const String& name, void* pData)
	{
		if (name == "FBO")
		{
			*static_cast<GLESFrameBufferObject**>(pData) = &mFB;
		}
	}
	
	void GLESFBORenderTexture::swapBuffers(bool waitForVSync)
	{
		mFB.swapBuffers();
	}
	
#define PROBE_SIZE 16

static const GLenum stencilFormats[] =
{
	GL_NONE,
	GL_STENCIL_INDEX8_OES
};

static const size_t stencilBits[] = 
{
	0, 8
};
#define STENCILFORMAT_COUNT (sizeof(stencilFormats) / sizeof(GLenum))

static const GLenum depthFormats[] = 
{
	GL_NONE,
	GL_DEPTH_COMPONENT16_OES,
	GL_DEPTH_COMPONENT24_OES,
	GL_DEPTH24_STENCIL8_OES
};

static const size_t depthBits[] =
{
	0, 26, 24, 24
};
#define DEPTHFORMAT_COUNT (sizeof(depthFormats) / sizeof(GLenum))

	GLESFBOManager::GLESFBOManager()
	{
		detectFBOFormats();
		
		glGenFramebuffersOES(1, &mTempFBO);
		GL_CHECK_ERROR;
	}
	
	GLESFBOManager::~GLESFBOManager()
	{
		if (!mRenderBufferMap.empty())
		{
			LogManager::getSingleton().logMessage("GL: Warning! GLESFBOManager destructor called, but not all renderbuffers were released.");
		}
		
		glDeleteFramebuffersOES(1, &mTempFBO);
		GL_CHECK_ERROR;
	}
	
	GLuint GLESFBOManager::_tryFormat(GLenum depthFormat, GLenum stencilFormat)
	{
		GLuint status, depthRB = 0, stencilRB = 0;
		
		if (depthFormat != GL_NONE)
		{
			glGenRenderbuffersOES(1, &depthRB);
			
			glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRB);
			
			glRenderbufferStorageOES(GL_RENDERBUFFER_OES, depthFormat,
									 PROBE_SIZE, PROBE_SIZE);
			
			glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
										 GL_RENDERBUFFER_OES, depthRB);
		}
		
		if (stencilFormat != GL_NONE)
		{
			glGenRenderbuffersOES(1, &stencilRB);
			
			glBindRenderbufferOES(GL_RENDERBUFFER_OES, stencilRB);
			
			glRenderbufferStorageOES(GL_RENDERBUFFER_OES, stencilFormat,
									 PROBE_SIZE, PROBE_SIZE);
			
			glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES,
										 GL_RENDERBUFFER_OES, stencilRB);
		}
		
		status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
		
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, 0);
		
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, 0);
		
		if (depthRB)
			glDeleteRenderbuffersOES(1, &depthRB);
			
		if (stencilRB)
			glDeleteRenderbuffersOES(1, &stencilRB);
			
		return status == GL_FRAMEBUFFER_COMPLETE_OES;
	}
	
	bool GLESFBOManager::_tryPackedFormat(GLenum packedFormat)
	{
		GLuint packedRB;
		
		glGenRenderbuffersOES(1, &packedRB);
		
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, packedRB);
		
		glRenderbufferStorageOES(GL_RENDERBUFFER_OES, packedFormat, PROBE_SIZE, PROBE_SIZE);
		
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
			GL_RENDERBUFFER_OES, packedRB);
			
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES,
			GL_RENDERBUFFER_OES, packedRB);
			
		GLuint status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
		
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, 0);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, 0);
		glDeleteRenderbuffersOES(1, &packedRB);
		
		return status == GL_FRAMEBUFFER_COMPLETE_OES;
	}
	
	void GLESFBOManager::detectFBOFormats()
	{
		GLuint fb, tid;
		GLenum target = GL_TEXTURE_2D;
		
		for (size_t x = 0; x < PF_COUNT; ++x)
		{
			mProps[x].valid = false;
			
			GLenum fmt = GLESPixelUtil::getGLInternalFormat((PixelFormat)x);
			if (fmt == GL_NONE && x != 0)
				continue;
				
			if (PixelUtil::isCompressed((PixelFormat)x))
				continue;
				
			glGenFramebuffersOES(1, &fb);
			GL_CHECK_ERROR;
			glBindFramebufferOES(GL_FRAMEBUFFER_OES, fb);
			GL_CHECK_ERROR;
			if (fmt != GL_NONE)
			{
				glGenTextures(1, &tid);
				GL_CHECK_ERROR;
				glBindTexture(target, tid);
				GL_CHECK_ERROR;
				
				glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
				GL_CHECK_ERROR;
				glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
				GL_CHECK_ERROR;
				glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
				GL_CHECK_ERROR;
				glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
				GL_CHECK_ERROR;
				
				glTexImage2D(target, 0, fmt, PROBE_SIZE, PROBE_SIZE, 0, fmt, GL_UNSIGNED_BYTE, 0);
				GL_CHECK_ERROR;
				glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
							target, tid, 0);
				GL_CHECK_ERROR;
			}
			
			GLuint status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
			GL_CHECK_ERROR;
			
			if (fmt == GL_NONE || status == GL_FRAMEBUFFER_COMPLETE_OES)
			{
				mProps[x].valid = true;
				StringUtil::StrStreamType str;
				str << "FBO " << PixelUtil::getFormatName((PixelFormat)x)
					<< " depth/stencil support: ";
				
				for (size_t depth = 0; depth < DEPTHFORMAT_COUNT; ++depth)
				{
					if (depthFormats[depth] != GL_DEPTH24_STENCIL8_OES)
					{
						for (size_t stencil = 0; stencil < STENCILFORMAT_COUNT; ++stencil)
						{
							if (_tryFormat(depthFormats[depth], stencilFormats[stencil]))
							{
								str << "D" << depthBits[depth] << "S" << stencilBits[stencil] << " ";
								FormatProperties::Mode mode;
								mode.depth = depth;
								mode.stencil = stencil;
								mProps[x].modes.push_back(mode);
							}
						}
					}
					else
					{
						if (_tryPackedFormat(depthFormats[depth]))
						{
							str << "Packed-D" << depthBits[depth] << "S" << 8 << " ";
							FormatProperties::Mode mode;
							mode.depth = depth;
							mode.stencil = 0;
							mProps[x].modes.push_back(mode);
						}
					}
				}
				LogManager::getSingleton().logMessage(str.str());
			}
			
#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE
			glBindFramebufferOES(GL_FRAMEBUFFER_OES, 1);
#else
			glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
#endif
			GL_CHECK_ERROR;
			glDeleteFramebuffersOES(1, &fb);
			GL_CHECK_ERROR;
			
			if (fmt != GL_NONE)
				glDeleteTextures(1, &tid);
			GL_CHECK_ERROR;
		}
		
		String fmtstring;
		for (size_t x = 0; x < PF_COUNT; ++x)
		{
			if (mProps[x].valid)
				fmtstring += PixelUtil::getFormatName((PixelFormat)x) + " ";
		}
		LogManager::getSingleton().logMessage("[GL] : Valid FBO targets " + fmtstring);
	}
	
	void GLESFBOManager::getBestDepthStencil(GLenum internalFormat, GLenum *depthFormat, GLenum *stencilFormat)
	{
		const FormatProperties &props = mProps[internalFormat];
		size_t bestmode = 0;
		int bestscore = -1;
		for (size_t mode = 0; mode < props.modes.size(); mode++)
		{
			int desirability = 0;
			if (props.modes[mode].stencil)
				desirability += 1000;
			if (props.modes[mode].depth)
				desirability += 2000;
			if (depthBits[props.mode[mode].depth] == 24)
				desirability += 500;
			if (depthFormats[props.modes[mode].depth] == GL_DEPTH24_STENCIL8_OES)
				desirability += 5000;
			desirability += stencilBits[props.modes[mode].stencil] + depthBits[props.modes[mode].depth];
			
			if (desirability > bestscore)
			{
				bestscore = desirability;
				bestmode = mode;
			}
		}
		*depthFormat = depthFormats[props.modes[bestmode].depth];
		*stencilFormat = stencilFormats[props.modes[bestmode].stencil];
	}
	
	GLESFBORenderTexture *GLESFBOManager::createRenderTexture(const String &name,
		const GLESSurfaceDesc &target, bool writeGamma, uint fsaa)
	{
		GLESFBORenderTexture *retval = OGRE_NEW GLESFBORenderTexture(this, name, target, writeGamma, fsaa);
		return retval;
	}
	
	MultiRenderTarget *GLESFBOManager::createMultiRenderTarget(const String &name)
	{
		return OGRE_NEW GLESFBOMultiRenderTarget(this, name);
	}
	
	void GLESFBOManager::bind(RenderTarget* target)
	{
		GLESFramebufferObject *fbo = 0;
		target->getCustomAttribute("FBO", &fbo);
		if (fbo)
			fbo->bind();
		else
#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, 1);
#else
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
#endif
		GL_CHECK_ERROR;
	}
	
	GLESSurfaceDesc GLESFBOManager::requestRenderBuffer(GLenum format, size_t width, size_t height, uint fsaa)
	{
		GLESSurfaceDesc retval;
		retval.buffer = 0;
		if (format != GL_NONE)
		{
			RBFormat key(format, width, height, fsaa);
			RenderBufferMap::iterator it = mRenderBufferMap.find(key);
			if (it != mRenderBufferMap.end())
			{
				retval.buffer = it->second.buffer;
				retval.zoffset = 0;
				retval.numSamples = fsaa;
				++it->second.refcount;
			}
			else
			{
				GLESRenderBuffer *rb = OGRE_NEW GLESRenderBuffer(format, width, height, fsaa);
				mRenderBufferMap[key] = RBRef(rb);
				retval.buffer = rb;
				retval.zoffset = 0;
				retval.numSamples = fsaa;
			}
		}
		return retval;
	}
	
	void GLESFBOManager::requestRenderBuffer(const GLESSurfaceDesc &surface)
	{
		if (surface.buffer == 0)
			return;
		RBFormat key(surface.buffer->getGLFormat(), surface.buffer->getWidth(), surface.buffer->getHeight(), surface.numSamples);
		RenderBufferMap::iterator it = mRenderBufferMap.find(key);
		assert(it != mRenderBufferMap.end());
		if (it != mRenderBufferMap.end())
		{
			assert(it->second.buffer == surface.buffer);
			++it->second.refcount;
		}
	}
	void GLESFBOManager::releaseRenderBuffer(const GLESSurfaceDesc &surface)
	{
		if (surface.buffer == 0)
			return;
		RBFormat key(surface.buffer->getGLFormat(), surface.buffer->getWidth(), surface.buffer->getHeight(), surface.numSamples);
		RenderBufferMap::iterator it = mRenderBufferMap.find(key);
		if (it != mRenderBufferMap.end())
		{
			--it->second.refcount;
			if (it->second.refcount == 0)
			{
				OGRE_DELETE it->second.buffer;
				mRenderBufferMap.erase(it);
			}
		}
	}
}
